package graphic;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.MovieClip;
import flash.display.Shape;
import flash.display.Sprite;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.Lib;
import flash.system.ApplicationDomain;
import flash.Vector;
import haxe.xml.Fast;
import phx.Body;
import phx.Circle;
import phx.Polygon;
import phx.Vector;

/**
 * @author Damien Mabin
 */
class RenderObject extends Sprite
{
	private static var s_RessourceManager	: RessourceManager = RessourceManager.GetInstance();
	
	private var m_Animations				: Hash<RenderObjectAnimation>;
	private var m_PlayingAnimations			: Array<{ animation : RenderObjectAnimation, loop : Int }>;
	public  var m_Bitmap					: Bitmap;
	private var m_PhysicalBody				: Body;	
	
	private var m_AppDomain					: ApplicationDomain;
	private var m_ModelName					: String;
	private var m_File						: String;
	
	public function new() 
	{
		super();
		m_AppDomain = null;
		m_File = null;
		m_ModelName = null;
		m_Animations = new Hash<RenderObjectAnimation>();
		m_PhysicalBody = new Body(0, 0);
		m_PlayingAnimations = new Array<{ animation : RenderObjectAnimation, loop : Int }>();
		var testbmd:BitmapData = new BitmapData(100, 100, false, 0x00FF00);
		m_Bitmap = new Bitmap(testbmd);
		this.addChild(m_Bitmap);
	}
	
	public function LoadFromDisplayObjectContainer(_DOC:DisplayObjectContainer,_File : String, _AppDomain : ApplicationDomain, _ModelName : String):Void
	{
		m_File = _File;
		m_ModelName = _ModelName;
		m_AppDomain = _AppDomain;
		
		// We load every animations :
		for (i in 0 ... _DOC.numChildren)
		{
			var animation : MovieClip = cast(_DOC.getChildAt(i), MovieClip);
			if (animation != null)
			{
				#if debug
					trace("loading animations : " + animation.name);
				#end
				m_Animations.set(animation.name, new RenderObjectAnimation(animation));
			}
		}
		PlayAnimation("Default", -1);
		#if debug
			trace("Load complete");
		#end
	}
	
	public inline function GetPhysicalBody():Body
	{
		return m_PhysicalBody;
	}
	
	public function Clone():RenderObject
	{
		var res : RenderObject = new RenderObject();
		
		for (animName in m_Animations.keys())
		{
			res.m_Animations.set(animName, m_Animations.get(animName).Clone());
		}
		
		res.m_ModelName = m_ModelName;
		res.m_AppDomain = m_AppDomain;
		res.m_File = m_File;
		res.name = this.name;
		res.scaleX = this.scaleX;
		res.scaleY = this.scaleY;
		res.rotation = this.rotation;
		res.PlayAnimation("Default", -1);
		
		res.m_PhysicalBody = new Body(m_PhysicalBody.x, m_PhysicalBody.y);
		res.m_PhysicalBody.setAngle(res.rotation * Math.PI / 180);
		for (shape in m_PhysicalBody.shapes)
		{
			switch(shape.type)
			{
				case phx.Shape.POLYGON : 
				{
					var points : Array<phx.Vector> = new Array<phx.Vector>();
					
					var pointIte : phx.Vector = shape.polygon.verts;
					do
					{
						points.push(new phx.Vector(pointIte.x, pointIte.y));
						pointIte = pointIte.next;
					}
					while (pointIte != null);
					
					res.m_PhysicalBody.addShape(new Polygon(points, new phx.Vector(0, 0)));
				}
				case phx.Shape.CIRCLE :
				{
					res.m_PhysicalBody.addShape(new Circle(shape.circle.r, new phx.Vector(0, 0)));
				}
			}
		}
		return res;
	}
	
	public inline function SetPosition(_X:Float, _Y:Float):Void
	{
		m_PhysicalBody.x = _X;
		m_PhysicalBody.y = _Y;
		
	}
	
	public inline function GetPosition():Point
	{
		return new Point(m_PhysicalBody.x, m_PhysicalBody.y);
	}
	
	/**
	 * The purpose of the function is to update the display of the object at the enterFrame.
	 * @param	_Delta the Time between this call and the former one in millisecond.
	 */
	public inline function Update(_Delta:Int):Void
	{
		// Update de l'animation
		for (anim in m_PlayingAnimations)
		{
			if (anim.animation.Update(_Delta) && anim.loop > 0)
			{
				anim.loop--;
			}
			if (anim.loop == -1 || anim.loop > 0)
			{
				m_Bitmap.bitmapData = anim.animation.m_CurrentFrame;
				m_Bitmap.x = -m_Bitmap.bitmapData.width / 2;
				m_Bitmap.y = -m_Bitmap.bitmapData.height / 2;
			}
			else
			{
				m_PlayingAnimations.remove(anim);
			}
		}
		
		var matrix:Matrix = new Matrix();
		// Update de la position par rapport a la forme physique :
		matrix.rotate(m_PhysicalBody.a);
		matrix.translate(m_PhysicalBody.x,  m_PhysicalBody.y);
		
		
		this.transform.matrix = matrix;
	}
	
	/**
	 * @param	_Name The name of the animation. If no animation are binded to this name, nothing happen.
	 * @param	_Loop If you want the animation to be played again when it's finished.
	 * @return true if sucess, false if animation is'nt finded.
	 */
	public function PlayAnimation (_Name : String, _Loop : Int) : Bool
	{
		var animation:RenderObjectAnimation = m_Animations.get(_Name);
		if (animation == null)
		{
			return false;
		}
		else
		{
			animation.Start();
			m_PlayingAnimations.push({ animation : animation, loop : _Loop });
			return true;
		}
	}
	
	public function SaveXML():Xml
	{
		var res : Xml = Xml.createElement("RenderObject");
		res.set("instanceName", this.name);
		res.set("modelName", this.m_ModelName);
		res.set("filePath", this.m_File);
		res.set("x", "" + m_PhysicalBody.x);
		res.set("y", "" + m_PhysicalBody.y);
		res.set("scale", "" + this.scaleX);
		res.set("heading", "" + this.rotation);
		res.set("staticBody", "" + m_PhysicalBody.isStatic);
		
		// Sauvegarde du body physique :
		if (m_PhysicalBody != null)
		{
			var bodyXml : Xml = Xml.createElement("Body");
			res.addChild(bodyXml);
			
			for (physShape in m_PhysicalBody.shapes)
			{
				var shapeXml : Xml = Xml.createElement("Shape");
				bodyXml.addChild(shapeXml);
				
				switch(physShape.type)
				{
					case phx.Shape.POLYGON :
					{
						shapeXml.set("type", "" + phx.Shape.POLYGON);
						var pointIte : phx.Vector = physShape.polygon.verts;
						do
						{
							var pointXml : Xml = Xml.createElement("Point");
							shapeXml.addChild(pointXml);
							
							pointXml.set("x", "" + pointIte.x);
							pointXml.set("y", "" + pointIte.y);
							pointIte = pointIte.next;
						}
						while (pointIte != null);
					}
					case phx.Shape.CIRCLE : 
					{
						shapeXml.set("type", "" + phx.Shape.CIRCLE);
						shapeXml.set("radius", "" + physShape.circle.r);
					}
				}
			}
		}
		return res;
	}
	
	public static function LoadXML(_Xml:Fast):RenderObject
	{
		var res : RenderObject = s_RessourceManager.InstanciateRenderObject(_Xml.att.modelName);
		res.name = _Xml.att.instanceName;
		res.scaleX = res.scaleY = Std.parseFloat(_Xml.att.scale);
		res.rotation  = Std.parseFloat(_Xml.att.heading);
		
		// Loading of physical body is defined
		if (_Xml.hasNode.Body)
		{
			res.m_PhysicalBody = new Body(res.x, res.y);
			res.m_PhysicalBody.isStatic = _Xml.att.staticBody == "true";
			res.m_PhysicalBody.setAngle(res.rotation * Math.PI / 180);
			if (_Xml.node.Body.hasNode.Shape)
			{
				for (shapeNode in _Xml.node.Body.nodes.Shape)
				{
					switch(Std.parseInt(shapeNode.att.type))
					{
						case phx.Shape.POLYGON :
						{
							var points:Array<phx.Vector> = new Array<phx.Vector>();
							for (pointNode in shapeNode.nodes.Point)
							{
								points.push(new phx.Vector(Std.parseFloat(pointNode.att.x), Std.parseFloat(pointNode.att.y)));
							}
							res.m_PhysicalBody.addShape(new Polygon(points, new phx.Vector(0, 0)));
						}
						case phx.Shape.CIRCLE :
						{
							res.m_PhysicalBody.addShape(new Circle(Std.parseFloat(shapeNode.att.radius), new phx.Vector(0, 0)));
						}
					}
				}
			}
		}
		res.SetPosition(Std.parseFloat(_Xml.att.x), Std.parseFloat(_Xml.att.y));
		return res;
	}
	
	public function GetModelName():String
	{
		return m_ModelName;
	}
}